查看原文
其他

TensorFlow 的 C++ API

Google TensorFlow 2021-07-27

TensorFlow 的 C++ API 提供了用于构建和执行数据流图的机制。该 API 旨在实现简洁性:它使用 “函数式” 构造样式清晰表达图操作(包括轻松指定名称、设备放置等),可以高效地运行生成的图,并用几行代码提取所需结果。本指南介绍了使用 C++ 开始构建和执行 TensorFlow 图需要了解的基本概念和数据结构。


C++ API 仅适用于 TensorFlow bazel build。如果您需要单独使用,请使用 C API。要详细了解如何将 TensorFlow 作为子项目纳入其中(而不是在 TensorFlow 中构建项目,如本例所示),请查看这些 说明(https://docs.bazel.build/versions/master/external.html?hl=zh-CN)



基础知识

我们从一个简单的示例开始,该示例说明了如何使用 C++ API 构建和执行图。

// tensorflow/cc/example/example.cc

#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"

int main() {
  using namespace tensorflow;
  using namespace tensorflow::ops;
  Scope root = Scope::NewRootScope();
  // Matrix A = [3 2; -1 0]
  auto A = Const(root, { {3.f, 2.f}, {-1.f, 0.f} });
  // Vector b = [3 5]
  auto b = Const(root, { {3.f, 5.f} });
  // v = Ab^T
  auto v = MatMul(root.WithOpName("v"), A, b, MatMul::TransposeB(true));
  std::vector<Tensor> outputs;
  ClientSession session(root);
  // Run and fetch v
  TF_CHECK_OK(session.Run({v}, &outputs));
  // Expect outputs[0] == [19; -3]
  LOG(INFO) << outputs[0].matrix<float>();
  return 0;
}


将此示例代码放在克隆的 TensorFlow GitHub 代码库内的 tensorflow/cc/example/example.cc 文件中。另外,将 BUILD 文件放在同一目录中,其中包含以下内容:

load("//tensorflow:tensorflow.bzl", "tf_cc_binary")

tf_cc_binary(
    name = "example",
    srcs = ["example.cc"],
    deps = [
        "//tensorflow/cc:cc_ops",
        "//tensorflow/cc:client_session",
        "//tensorflow/core:tensorflow",
    ],
)


使用 tf_cc_binary 而不是 Bazel 的原生 cc_binary 从 libtensorflow_framework.so 中关联所需的符号。您应该能够使用以下命令编译并运行此示例(确保首先在编译沙盒中运行 ./configure):

bazel run -c opt //tensorflow/cc/example:example


此示例演示了 C++ API 的一些重要功能,例如:

  • 从 C++ 嵌套初始化器列表构造张量常量

  • 构造和命名 TensorFlow 操作

  • 为操作构造函数指定可选属性

  • 从 TensorFlow 会话执行和提取张量值


我们将在下面深入介绍各项内容。



图构造

Scope

tensorflow::Scope 是存储图构造的当前状态的主要数据结构。Scope 充当正在构造的图的句柄,并用于存储 TensorFlow 操作属性。Scope 对象是操作构造函数的第一个参数,使用给定 Scope 作为其第一个参数的操作会继承该 Scope 的属性(例如通用名称前缀)。多个 Scope 可以引用同一个图,下文对此进行了详细说明。


调用 Scope::NewRootScope 可以创建一个新的 Scope 对象。这会创建一些资源,例如将操作添加到的图。这还会创建一个 tensorflow::Status 对象,用于指示在构造操作时遇到的错误。Scope 类具有值语义,因此,Scope 对象可以随意复制和传递。


Scope::NewRootScope 返回的 Scope 对象称为根作用域。可以通过调用 Scope 类的各个成员函数从根作用域构造 “子” 作用域,从而形成作用域层次结构。子作用域会继承父作用域的所有属性,通常会添加或更改一个属性。例如,NewSubScope(name) 会将 name 附加到使用返回的 Scope 对象创建的操作的名称前缀。


以下是由 Scope 对象控制的一些属性:

  • 操作名称

  • 操作的一组控件依赖项

  • 操作的设备放置位置

  • 操作的内核属性


要查看允许您使用新属性创建子作用域的成员函数的完整列表,请参阅 tensorflow::Scope。


操作构造函数

您可以使用操作构造函数创建图操作,每个 TensorFlow 操作一个 C++ 类。与使用蛇形命名法命名操作构造函数的 Python API 不同,C++ API 使用驼峰式命名法以符合 C++ 编码风格。例如,MatMul 操作具有同名的 C++ 类。

使用这种每个操作一个类方法,可以按如下方式构造操作(但不建议这样做):

// Not recommended
MatMul m(scope, a, b);


建议您在构造操作时使用以下 “函数式” 样式:

// Recommended
auto m = MatMul(scope, a, b);


所有操作构造函数的第一个参数始终是 Scope 对象。其余参数是张量输入和必需属性。


对于可选参数,构造函数具有允许可选属性的可选参数。对于带有可选参数的操作,构造函数的最后一个可选参数是一个名为 [operation]:Attrs 的 struct 类型,其中包含每个可选属性的数据成员。您可以通过多种方式构造此类 Attrs:


  • 要指定单个可选属性,您可以使用 C++ 类中为该操作提供的 static 函数构造 Attrs 对象。例如:

auto m = MatMul(scope, a, b, MatMul::TransposeA(true));


  • 您可以通过将 Attrs 结构体中可用的函数连在一起来指定多个可选属性。例如:

auto m = MatMul(scope, a, b, MatMul::TransposeA(true).TransposeB(true));

// Or, alternatively
auto m = MatMul(scope, a, b, MatMul::Attrs().TransposeA(true).TransposeB(true));


系统会以不同方式处理操作的参数和返回值,具体取决于操作的类型:


  • 对于返回单个张量的操作,操作对象返回的对象可以直接传递给其他操作构造函数。例如:

auto m = MatMul(scope, x, W);
auto sum = Add(scope, m, bias);


  • 对于生成多个输出的操作,操作构造函数返回的对象具有与每个输出对应的一个成员。这些成员的名称与操作的 OpDef 中存在的名称相同。例如:

auto u = Unique(scope, a);
// u.y has the unique values and u.idx has the unique indices
auto m = Add(scope, u.y, b);


  • 生成列表类型输出的操作会返回可以使用 [] 操作符编制索引的对象。该对象也可以直接传递给需要列表类型输入的其他构造函数。例如:

auto s = Split(scope, 0, a, 2);
// Access elements of the returned list.
auto b = Add(scope, s[0], s[1]);
// Pass the list as a whole to other constructors.
auto c = Concat(scope, s, 0);


常量

您可以将许多不同类型的 C++ 值直接传递给张量常量。您可以通过用各种 C++ 值调用 tensorflow::ops::Const 函数来显式创建张量常量。例如:


  • 标量

auto f = Const(scope, 42.0f);
auto s = Const(scope, "hello world!");


  • 嵌套初始化器列表

// 2x2 matrix
auto c1 = Const(scope, { {1, 2}, {2, 4} });
// 1x3x1 tensor
auto c2 = Const(scope, { { {1}, {2}, {3} } });
// 1x2x0 tensor
auto c3 = ops::Const(scope, { { {}, {} } });


  • 明确指定的形状

// 2x2 matrix with all elements = 10
auto c1 = Const(scope, 10, /* shape */ {2, 2});
// 1x3x2x1 tensor
auto c2 = Const(scope, {1, 2, 3, 4, 5, 6}, /* shape */ {1, 3, 2, 1});


您可以通过以下任一方法直接将常量传递给其他操作构造函数:使用 Const 函数显式构造一个常量,或根据以上任一类型的 C++ 值隐式构造常量。例如:

// [1 1] * [41; 1]
auto x = MatMul(scope, { {1, 1} }, { {41}, {1} });
// [1 2 3 4] + 10
auto y = Add(scope, {1, 2, 3, 4}, 10);



图执行

执行图时,您需要一个会话。C++ API 提供了一个 tensorflow::ClientSession 类,它将执行由操作构造函数创建的操作。TensorFlow 将自动确定需要执行图的哪些部分,以及需要提供哪些值。例如:

Scope root = Scope::NewRootScope();
auto c = Const(root, { {1, 1} });
auto m = MatMul(root, c, { {41}, {1} });

ClientSession session(root);
std::vector<Tensor> outputs;
session.Run({m}, &outputs);
// outputs[0] == {42}


同理,可以将操作构造函数返回的对象用作参数,以指定在执行图时提供的值。此外,可以使用用于指定张量常量的不同类型的 C++ 值来指定要提供的值。例如:

Scope root = Scope::NewRootScope();
auto a = Placeholder(root, DT_INT32);
// [3 3; 3 3]
auto b = Const(root, 3, {2, 2});
auto c = Add(root, a, b);
ClientSession session(root);
std::vector<Tensor> outputs;

// Feed a <- [1 2; 3 4]
session.Run({ {a, { {1, 2}, {3, 4} } } }, {c}, &outputs);
// outputs[0] == [4 5; 6 7]


要详细了解如何使用执行输出,请参阅 tensorflow::Tensor 文档。



更多 AI 相关阅读:



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存